/****************************************************************************
|*  DirectLighting.cpp
|*
|*  A integrator only considering direct lighting
|*
\***********************************************************/

#include "DirectLighting.h"

#include <utils/Vector4.h>
#include <Scene.h>

#define _USE_MATH_DEFINES
#include <cmath>

#include <utils/MonteCarloUtilities.h>
#include <sceneelements/geometry/MeshTriangle.h>
#include <cstdio>

DirectLighting::DirectLighting()
{
	// TODO: initialize vector data members
	/*
	m_lights = new vector<Mesh *>;
	partialAreaSum = new vector<double>;
	*/
	areaSum = 0;
}

Vector4 DirectLighting::integrate( const Ray& ray )
{
	return integrateLightSampling(ray);
}

Vector4 DirectLighting::integrateConstant(const Ray& ray) {
	IntersectionData* iData = m_scene->intersect(ray);
	Vector4 color;

	if(iData) { // successful intersection test
		color = iData->material->diffuse;
	} else color = m_scene->getBackground();

	delete iData;

	return color.clamp01();
}

Vector4 DirectLighting::integrateSamplingBRDF( const Ray& ray )
{
	Vector4 color; // The scene bg is assumed to have emission zero
	IntersectionData* iData = m_scene->intersect(ray);

	if(iData) { // successful intersection test
		Vector3 p = iData->position;
		int N = m_nSamples;
		Vector4 sum;

		color = iData->material->emission;

		assert(N > 0);
		for(int i = 0; i < N; ++i) {
			Vector3 s = MonteCarloUtilities::uniformSampleHemisphere(
					iData->surfaceNormal);
			Ray r = Ray(p, s);
			r.min_t = Ray::epsilon_t;

			IntersectionData *iData2 = m_scene->intersect(r);
			if(iData2) {
				Vector4 L = iData2->material->emission;
				// check for correct side of light emission
				if(s.dot(iData2->surfaceNormal) < 0) {
					double cos_th = abs(s.dot(iData->surfaceNormal));
					L *= cos_th;
					sum += L;
				}
			}
			delete iData2;
		}
		sum *= 2*M_PI/(double)N;
		// The division by M_PI is done in order to conserve the energy
		// as seen on the CS-440 forum
		// or at: http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
		sum = sum.componentMul(iData->material->diffuse/M_PI);
		color += sum;
	}

	color[3] = 1; // We don't consider the alpha channel

	// Cleanup
	delete iData; // it seems we have to free this ourselves

	return color.clamp01();
}

Vector4 DirectLighting::integrateLightSampling(const Ray& ray)
{
	Vector4 color; // the constructor initializes to zero
	IntersectionData* iData = m_scene->intersect(ray);

	if(iData) {
		Vector3 p = iData->position;
		int N = m_nSamples;
		Vector4 sum;

		color = iData->material->emission;

		assert(N > 0);
		for(int i = 0; i < N; ++i) {
			IntersectionData iData2 = sampleLightsFast();
			Vector3 q = iData2.position;
			Vector3 d = (q-p).normalize(); // direction
			Vector4 L = iData2.material->emission;
			Ray r(p, d);
			r.min_t = Ray::epsilon_t;
			r.max_t = (q-p).length()-Ray::epsilon_t;
			if(m_scene->fastIntersect(r))
				continue;
			double cos_th = d.dot(iData->surfaceNormal);
			double cos_th2 = (-d).dot(iData2.surfaceNormal);
			if(cos_th < 0 || cos_th2 < 0)
				continue;
			double dsq = (q-p).lengthSquared();
			sum += L*cos_th*cos_th2/dsq ;
		}

		sum *= areaSum/((double)N*M_PI);
		sum = sum.componentMul(iData->material->diffuse);
		color += sum;
	}

	color[3] = 1;
	delete iData;

	return color.clamp01();
}

void DirectLighting::setScene( Scene* scene )
{
	m_scene  = scene;

	/*
	 * Exercise 2.2 
	 * Fill m_lights with all meshes having materials with a non-zero RGB emission.
	 */
	m_lights.clear();
	partialAreaSum.clear();

	vector<Mesh *> meshList = scene->getMeshes();
	areaSum = 0;
	for(int i = 0; i < meshList.size(); ++i) {
		Vector4 e = meshList[i]->getMaterial()->emission;
		double eps = 0.1;
		if(e[0] > eps || e[1] > eps || e[2] > eps) {
			m_lights.push_back(meshList[i]);
			areaSum += meshList[i]->getArea();
			partialAreaSum.push_back(areaSum);
		}
	}
	assert(m_lights.size() > 0);

	/*
	 * Needed for sampleLightsFast()
	 */
	double triSum = 0;
	partialTriSum.clear();
	for(int i = 0; i < m_lights.size(); ++i)
		for(int j = 0; j < m_lights[i]->numberOfFaces(); ++j) {
			MeshTriangle *tri = m_lights[i]->getFace(j);
			triSum += tri->getArea();
			partialTriSum.push_back(make_pair(triSum, tri));
		}
}

IntersectionData DirectLighting::sampleLights()
{
	double r = MonteCarloUtilities::mcRand()*areaSum;

	vector<double>::iterator it = lower_bound(partialAreaSum.begin(),
		partialAreaSum.end(), r); // binary search the light
	int i = it - partialAreaSum.begin(); // compute the light's index
	i -= (i == m_lights.size()); // avoid overflow

	/*
	int i;
	int N = m_lights.size();
	for(i = 0; i < N; ++i) {
		r -= m_lights[i]->getArea();
		if(r < 0)
			break;
	}
	i = (i == N)? N-1 : i;
	*/

	IntersectionData iData;
	m_lights[i]->sample(iData);

	return iData;
}

IntersectionData DirectLighting::sampleLightsFast()
{
	double r = MonteCarloUtilities::mcRand()*areaSum;

	vector<pair<double, MeshTriangle *> >::iterator it =
		lower_bound(partialTriSum.begin(), partialTriSum.end(),
			make_pair(r, partialTriSum[0].second)); // binary search the triangle
	if(it == partialTriSum.end())
		--it;

	IntersectionData iData;
	it->second->sample(iData);

	return iData;
}
